2012/06/26

Recent entries from same category

  1. RapidJSON や simdjson よりも速いC言語から使えるJSONライブラリ「yyjson」
  2. コメントも扱える高機能な C++ 向け JSON パーサ「jsoncpp」
  3. C++ で flask ライクなウェブサーバ「clask」書いた。
  4. C++ 用 SQLite3 ORM 「sqlite_orm」が便利。
  5. zsh で PATH に相対パスを含んだ場合にコマンドが補完できないのは意図的かどうか。

何に関連して記事を書こうと思った訳でもないです。たんなる一人コードリーディングです。
GNU CoreUtils に入ってる rm を読みました。
GNU Project Archives
http://ftp.gnu.org/gnu/coreutils/
読んだのは coreutils-8.17.tar.xz に入ってる src/rm.c
preserve_root 変数は 203行目にある main で int
main (int argc, char **argv)
{
  bool preserve_root = true;
  struct rm_options x;
true に初期化されていて319行目   if (x.recursive && preserve_root)
    {
      static struct dev_ino dev_ino_buf;
      x.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
      if (x.root_dev_ino == NULL)
        error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
               quote ("/"));
    }
で参照される。x.recursive は -r もしくは -R が指定された場合に true となる。つまり「--no-preserve-root」が指定されていない場合に入る。get_root_dev_ino は lib/root-dev-ino.c にある。   if (lstat ("/", &statbuf))
    return NULL;
ド頭で / を確認しているのでもしlstatに失敗したら上記の319行目のコードは       if (x.root_dev_ino == NULL)
        error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
               quote ("/"));
に遷移する。error 関数は lib/error.c にあり、そこから error_tail へ。   if (status)
    exit (status);
error 関数は EXIT_FAILURE (値1)で呼び出しているのでこの後はプログラムは終了。つまり --no-preserve-root を付けた場合で / のデバイス情報が取れる場合だけ削除処理に入る。
逆に上記の x.recursive が false つまり、-r や -R を指定しなかった場合、src/remove.c にある rm 関数に入る。rm 関数では引数 file は / として渡り、xfts_open 関数(fts_open の wrapper)へ渡り、ディレクトリリストの一部として rm_fts に渡る。/ はディレクトリなので rm_fts でいきなり入る switch 文   switch (ent->fts_info)
    {
    case FTS_D:         /* preorder directory */
      if (! x->recursive)
        {
          /* This is the first (pre-order) encounter with a directory.
             Not recursive, so arrange to skip contents.  */
          error (0EISDIR, _("cannot remove %s"), quote (ent->fts_path));
          mark_ancestor_dirs (ent);
          fts_skip_tree (fts, ent);
          return RM_ERROR;
        }
の FTS_D のケースに入る。ここには x.recursive は false で入ったはずなので、ブロック内のエラーメッセージが表示され、配下のファイルもスキップされる。関数の戻り値は RM_ERROR。rm 関数でエラーコード rm_status が s で更新される。ディレクトリリストとしては / だけなのでそのまま終了。戻り値は RM_ERROR。main に戻ってきて   exit (status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS);
EXIT_FAILURE で exit してプログラム終了。

-r --no-root-preserve の場合は       if (ent->fts_level == FTS_ROOTLEVEL)
        {
          if (strip_trailing_slashes (ent->fts_path))
            ent->fts_pathlen = strlen (ent->fts_path);

          /* If the basename of a command line argument is "." or "..",
             diagnose it and do nothing more with that argument.  */
          if (dot_or_dotdot (last_component (ent->fts_accpath)))
            {
              error (00, _("cannot remove directory: %s"),
                     quote (ent->fts_path));
              fts_skip_tree (fts, ent);
              return RM_ERROR;
            }

          /* If a command line argument resolves to "/" (and --preserve-root
             is in effect -- default) diagnose and skip it.  */
          if (ROOT_DEV_INO_CHECK (x->root_dev_ino, ent->fts_statp))
            {
              ROOT_DEV_INO_WARN (ent->fts_path);
              fts_skip_tree (fts, ent);
              return RM_ERROR;
            }
        }
上記で得た x.root_dev_ino を使いガードされている。
まとめると

GNU CoreUtils に入っている rm を使っている限り、--no-preserve-root を指定しないと / は消せない

事になる。冗長的に言うと # rm -r /$FOO は、対話型でないシェルスクリプトから起動したとしても、-f を付けたとしても、ユーザが root であったとしても関係無く、--no-preserve-root を付けないと消せない という事になり、さらに $FOO が宣言されていなくて # rm -r / になったとしても消せない、という事が分かった。

以上、コードリーディング終了。
Posted at by